package com.tdam2011.grupo05.services;

import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.ResultReceiver;
import android.util.Log;

import com.google.common.collect.Maps;
import com.tdam2011.grupo05.R;
import com.tdam2011.grupo05.control.app.TdamApplication;
import com.tdam2011.grupo05.control.connectivity.ConnectivityListener;
import com.tdam2011.grupo05.control.connectivity.ConnectivityListener.ConnectionListener;
import com.tdam2011.grupo05.control.notification.NotificationMessage;
import com.tdam2011.grupo05.control.task.HttpWorker;
import com.tdam2011.grupo05.database.manager.DataManager;
import com.tdam2011.grupo05.entities.message.MessageWeb;
import com.tdam2011.grupo05.entities.thread.ThreadWeb;
import com.tdam2011.grupo05.processor.GetMessageProcessor;
import com.tdam2011.grupo05.processor.Processor;
import com.tdam2011.grupo05.processor.RegisterUserProcessor;
import com.tdam2011.grupo05.processor.SendMessageProcessor;
import com.tdam2011.grupo05.services.ServiceHelper.Listener;
import com.tdam2011.grupo05.services.constant.MessageConstant;
import com.tdam2011.grupo05.services.constant.ServiceActions;
import com.tdam2011.grupo05.services.constant.ServiceResult;
import com.tdam2011.grupo05.ui.activities.message.MessageWebActivity;
import com.tdam2011.grupo05.utils.preference.PreferencesHelper;

public class MessageService extends Service {

	private static final String TAG = "MessageService";

	// TODO: refactor ==> preferences
	private String url = "http://10.0.2.2:8080/MessageSender/";

	private ServiceHelper mService;

	private DataManager manager;

	private PreferencesHelper mHelper;

	private final ExecutorService mExecutor = Executors
			.newSingleThreadExecutor();

	private final ScheduledExecutorService mScheduledExecutor = Executors
			.newScheduledThreadPool(2);

	private final Map<Long, HttpWorker> mQueue = Maps.newHashMap();

	private ScheduledFuture<?> mFuture;

	private ScheduledFuture<?> mPending;

	private Processor sendMessage;

	private Processor getMessage;

	private Processor registerUser;

	private MessageWebActivity activity;

	private ConnectivityListener mConnectivityListener;

	private final PendingMessage pendingWorker = new PendingMessage();

	private boolean lastStatePoll = false;

	private static int mId = 0;

	private final ConnectionListener mConnectionListener = new ConnectionListener() {

		@Override
		public void connectionAlive() {
			startPolling(mHelper.getSyncTime() * 1000);
			startCheckingPending(10000);
		}

		@Override
		public void connectionDead() {
			stopPolling();
			stopCheckingPending();
		}
	};

	private final ServiceHelper.Listener mListener = new Listener() {

		@Override
		public void sendMessage(MessageWeb m, ThreadWeb t) {
			((SendMessageProcessor) sendMessage).setMessageToSend(m, t);
			if (isNetworkingActive()) {

				asyncRequest(m.getTimestamp().getTime(), sendMessage);
			} else {
				((SendMessageProcessor) sendMessage).sendMessageLate();
				if (activity != null) {
					activity.showDialog(
							getResources().getString(R.string.message_error),
							" " + m.getText());
				}
			}
		}

		@Override
		public void registerUser(String username, String password) {
			((RegisterUserProcessor) registerUser).setData(mId++, username,
					password);
			asyncRequest(System.currentTimeMillis(), registerUser);
		}

	};

	@Override
	public IBinder onBind(Intent intent) {
		Log.i(TAG, "binding to service");
		return new MainBinder();
	}

	@Override
	public boolean onUnbind(Intent intent) {
		Log.i(TAG, "unbinding to service");
		activity = null;
		return true;
	}

	@Override
	public void onCreate() {
		super.onCreate();

		Log.d(TAG, "on create service");
		mService = ServiceHelper.getIntance(MessageService.this);
		mService.suscribeListener(mListener);
		manager = mService.getDataManager();
		mHelper = ((TdamApplication) getApplication()).getPreference();
		mConnectivityListener = new ConnectivityListener(MessageService.this);
		mConnectivityListener.setListener(mConnectionListener);
		mConnectivityListener.registerReceiver();
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {

		Log.i(TAG, "on start command");
		if (intent != null) {
			final String action = intent.getAction();
			int result = super.onStartCommand(intent, flags, startId);
			if (action == null) {
				stopSelf();
			} else if (ServiceActions.START_SERVICE_MESSAGE_ACTION
					.equals(action)) {
				if (lastStatePoll) {
					stopPolling();
					stopCheckingPending();
					lastStatePoll = false;
				}
				if (mHelper.getSyncState())
					startingServiceMessage();

			} else if (ServiceActions.STOP_SERVICE_MESSAGE_ACTION
					.equals(action)) {
				stopPolling();
				stopCheckingPending();
			} else if (ServiceActions.START_SERVICE.equals(action)) {
				registerUser = new RegisterUserProcessor(this);
			} else if (ServiceActions.RESET_SERVICE_MESSAGE_ACTION
					.equals(action)) {
				stopPolling();
				stopCheckingPending();
				lastStatePoll = false;
				if (mHelper.getSyncState())
					startingServiceMessage();
			}

			return result;
		} else {
			stopSelf();
			return START_NOT_STICKY;
		}
	}

	private void startingServiceMessage() {
		String username = mHelper.getUsername();
		String password = mHelper.getPassword();

		url = mHelper.getServerUrl();
		if (url.equals("")) {

			url = getResources().getString(R.string.server_default_value);
			mHelper.setServerUrl(url);
		}
		sendMessage = new SendMessageProcessor(manager, username, password,
				mReceiverSended);
		getMessage = new GetMessageProcessor(manager,
				((TdamApplication) getApplication()).getCurrentAccount(),
				mReceiverGet, mHelper);

		registerUser = new RegisterUserProcessor(this);

		lastStatePoll = true;

		startPolling(mHelper.getSyncTime() * 1000);
		startCheckingPending(mHelper.getReSyncTime() * 1000);
		;
	}

	private boolean asyncRequest(long idRequest, Processor p) {

		synchronized (mQueue) {
			Log.d(TAG, "queue size=" + mQueue.size());
			HttpWorker worker = getRequestTask(idRequest);
			if (null == worker) {
				worker = new HttpWorker(p, url);
				mQueue.put(idRequest, worker);
				mExecutor.submit(worker);
				return true;
			}
			mExecutor.submit(worker);
			return false;
		}
	}

	private HttpWorker getRequestTask(long idRequest) {
		synchronized (mQueue) {
			return mQueue.get(idRequest);
		}
	}

	private void requestComplete(long idRequest) {
		synchronized (mQueue) {

			mQueue.remove(idRequest);
			Log.w(TAG, "remove task, queue size=" + mQueue.size());
		}
	}

	@Override
	public void onDestroy() {
		Log.d(TAG, "destroy service");
		stopPolling();
		stopCheckingPending();
		mConnectivityListener.unregisterReceiver();
		super.onDestroy();

	}

	@Override
	public void onLowMemory() {
		Log.w(TAG, "onLowMemory");
		super.onLowMemory();
	}

	private synchronized void startPolling(long time) {
		if (mFuture == null && isNetworkingActive() && lastStatePoll) {

			Log.d(TAG, "start polling");
			HttpWorker pollWorker = new HttpWorker(getMessage, url);
			mFuture = mScheduledExecutor.scheduleWithFixedDelay(pollWorker, 0,
					10000, TimeUnit.MILLISECONDS);
		}
	}

	private synchronized void stopPolling() {
		if (mFuture != null) {
			Log.d(TAG, "stop polling");
			mFuture.cancel(true);
			mFuture = null;
			lastStatePoll = true;
		}
	}

	private synchronized void startCheckingPending(long time) {
		if (mPending == null && isNetworkingActive() && lastStatePoll) {

			Log.d(TAG, "startCheckingPending");
			mPending = mScheduledExecutor.scheduleWithFixedDelay(pendingWorker,
					0, time, TimeUnit.MILLISECONDS);
		}
	}

	private synchronized void stopCheckingPending() {
		if (mPending != null) {
			Log.d(TAG, "stopCheckingPending");
			mPending.cancel(true);
			mPending = null;
		}
	}

	private void sendNewMessageNotification(String from, String message, int id) {
		if (activity == null) {
			NotificationMessage.notifyNewMessageInbox(MessageService.this,
					from, message, id);
		}
	}

	private void sendErrorNotification(String from, String message, int id) {
		if (activity == null) {
			NotificationMessage.notifyMessageFail(MessageService.this, from,
					message, id);
		}
	}

	private boolean isNetworkingActive() {
		return (mConnectivityListener.isMobileConnected() | mConnectivityListener
				.isWifiConnected());
	}

	/**
	 * Result Receivers from Processors
	 */

	private final Handler mHandler = new Handler();

	private final ResultReceiver mReceiverSended = new ResultReceiver(mHandler) {
		protected void onReceiveResult(int resultCode, Bundle resultData) {
			switch (resultCode) {

			case ServiceResult.MESSAGE_SENDED_OK:
				Log.d(TAG, "Sended Message Ok");
				requestComplete(resultData
						.getLong(MessageConstant.TIMESTAMP, 0));
				break;

			case ServiceResult.MESSAGE_SENDED_FAIL:
				Log.e(TAG, "Sended Message Error");
				requestComplete(resultData
						.getLong(MessageConstant.TIMESTAMP, 0));
				break;

			default:
				throw new UnknownError("resultado desconocido: " + resultCode);
			}
		}
	};

	private final ResultReceiver mReceiverGet = new ResultReceiver(mHandler) {
		protected void onReceiveResult(int resultCode, Bundle resultData) {
			switch (resultCode) {
			case ServiceResult.MESSAGES_GET_OK:
				Log.d(TAG, "Get Message Ok");
				sendNewMessageNotification(
						resultData.getString(GetMessageProcessor.THREAD),
						resultData.getString(GetMessageProcessor.MESSAGE),
						(int) resultData.getLong(GetMessageProcessor.THREAD_ID));
				break;

			case ServiceResult.MESSAGES_GET_FAIL:
				Log.e(TAG, "Get Message Error");
				sendErrorNotification("Error", "Error en procesar consulta", 0);
				break;

			default:
				throw new UnknownError("resultado desconocido: " + resultCode);
			}
		}
	};

	public class MainBinder extends Binder {

		public void setActivity(MessageWebActivity activity) {
			MessageService.this.activity = activity;
		}
	}

	private class PendingMessage implements Runnable {

		@Override
		public void run() {
			LinkedList<MessageWeb> unsended = manager.getUnsendedMessage();
			Log.d(TAG, "messages to send=" + unsended.size());
			while (!unsended.isEmpty()) {
				try {

					MessageWeb m = unsended.removeFirst();
					ThreadWeb t = manager.getThread(m.getIdThread());
					Log.d(TAG, "resending message=" + m.getId());
					((SendMessageProcessor) sendMessage).setMessageToSend(m, t);
					asyncRequest(m.getTimestamp().getTime(), sendMessage);

					Thread.sleep(10 * 1000); // 10 seconds
				} catch (InterruptedException e) {
					Log.e(TAG, "ThreadSleep Exception", e);
				}
			}
		}
	}

}
